package edu.gatech.volt2.drdoommgt.system;

import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.NoResultException;
import javax.persistence.NonUniqueResultException;
import javax.persistence.Persistence;
import javax.persistence.Query;
import javax.swing.JOptionPane;

import org.hibernate.HibernateException;

import edu.gatech.volt2.drdoommgt.domain.Appointment;
import edu.gatech.volt2.drdoommgt.domain.Doctor;
import edu.gatech.volt2.drdoommgt.domain.Name;
import edu.gatech.volt2.drdoommgt.domain.Nurse;
import edu.gatech.volt2.drdoommgt.domain.Patient;
import edu.gatech.volt2.drdoommgt.domain.SysAdmin;
import edu.gatech.volt2.drdoommgt.domain.TreatmentRecord;
import edu.gatech.volt2.drdoommgt.domain.User;
import edu.gatech.volt2.drdoommgt.domain.UserType;
import edu.gatech.volt2.drdoommgt.exceptions.LoginException;


public class DataBaseAccessor 
{
	@javax.persistence.PersistenceContext private EntityManagerFactory emf = null;
	private EntityManager em = null;
	private String persistence_unit = "domain";

	public DataBaseAccessor(){
	}
	
	public DataBaseAccessor(String unit){
		this.persistence_unit = unit;
	}
	
	public void connectToDatabase()
	{
		if ( !isConnected() ){
			try
			{
				this.emf = Persistence.createEntityManagerFactory(this.persistence_unit);
				this.em = this.emf.createEntityManager(); 	// Retrieve an application managed entity manager
			}catch(Throwable t){
				t.printStackTrace();
				return;
			}
		}
	}
	
	public boolean isConnected(){
		return (this.em != null && this.emf != null && this.em.isOpen());
	}
	
	public void closeConnectionToDatabase()
	{
		this.em.close();
		this.emf.close();
	}
	
	
	public boolean save(Object objectToSave) {
		this.em.getTransaction().begin();
		this.em.persist(objectToSave);
		this.em.getTransaction().commit();
		return true;
	}
	
	public boolean update(Object objectToSave) {
		this.em.getTransaction().begin();
		this.em.merge(objectToSave);
		this.em.getTransaction().commit();
		return true;
	}

	public <T> T find(Class<T> arg0, Object arg1){
		this.em.getTransaction().begin();
		T found = this.em.find(arg0, arg1);
		this.em.getTransaction().commit();
		return found;
	}
	
	public <T> T find(Class<T> arg0, Object arg1, Map<String, Object> arg2){
		this.em.getTransaction().begin();
		T found = this.em.find(arg0, arg1, arg2);
		this.em.getTransaction().commit();
		return found;
	}
	
	@SuppressWarnings("unchecked")
	public <T> List<T> all(Class<T> clazz){
        List<T> objects = null;
        try {
    		this.em.getTransaction().begin();
            Query query = this.em.createQuery("from " + clazz.getName());
            objects = query.getResultList();
            this.em.getTransaction().commit();
        } catch (HibernateException e) {
            System.err.println(e);
        }
        return (List<T>) objects;
	}
	
	
	public boolean createUser(User user)
	{
		boolean created= false;
		
		this.em.getTransaction().begin();
		
		boolean duplicate= checkDuplicateEmail(user.getEmail());

		if(duplicate == false)
		{
			this.em.persist(user);
			created= true;
		}

		this.em.getTransaction().commit();
		
		return created;
	}
	public boolean createUser(Patient user)
	{

		boolean created= false;

		this.em.getTransaction().begin();
		
		boolean duplicate= checkDuplicateEmail(user.getEmail());

		if(duplicate == false)
		{
			this.em.persist(user);
			created= true;
		}

		this.em.getTransaction().commit();
		
		return created;
	}
	
	public boolean createUser(Doctor user)
	{

		boolean created= false;

		this.em.getTransaction().begin();
		
		boolean duplicate = checkDuplicateEmail(user.getEmail());

		if(duplicate == false)
		{
			this.em.persist(user);
			this.em.getTransaction().commit();
			return true;
		}
		this.em.getTransaction().commit();

		
		return false;
	}
	
	public boolean createUser(Nurse user)
	{

		boolean created= false;

		this.em.getTransaction().begin();
		
		boolean duplicate= checkDuplicateEmail(user.getEmail());

		if(duplicate == false)
		{
			this.em.persist(user);
			created= true;
		}

		this.em.getTransaction().commit();
		
		return created;
	}
	
	public boolean createUser(SysAdmin user)
	{

		boolean created= false;

		this.em.getTransaction().begin();
		
		boolean duplicate= checkDuplicateEmail(user.getEmail());

		if(duplicate == false)
		{
			this.em.persist(user);
			created= true;
		}

		this.em.getTransaction().commit();
		
		return created;
	}
	
	
	
	public void saveUser(User user)
	{
		this.em.getTransaction().begin();
		this.em.persist(user);
		this.em.getTransaction().commit();	
	}
	
	public void saveUser(Patient user)
	{
		this.em.getTransaction().begin();
		this.em.persist(user);
		this.em.getTransaction().commit();	
	}
	
	public void saveUser(Doctor user)
	{
		this.em.getTransaction().begin();
		this.em.persist(user);
		this.em.getTransaction().commit();	
	}
	
	public void saveUser(Nurse user)
	{
		this.em.getTransaction().begin();
		this.em.persist(user);
		this.em.getTransaction().commit();	
	}
	
	public void saveUser(SysAdmin user)
	{
		this.em.getTransaction().begin();
		this.em.persist(user);
		this.em.getTransaction().commit();	
	}
	
	
	
	public void removeUser(User user)
	{
		try
		{
			this.em.getTransaction().begin();
			User userx = em.find(User.class, user.getId());
			this.em.remove(userx); 
			this.em.getTransaction().commit();
		}
		finally
		{
			//do nothing
		}
	}
	public void removeUser(Patient user)
	{
		try
		{
			this.em.getTransaction().begin();
			User userx = em.find(Patient.class, user.getId());
			this.em.remove(userx); 
			this.em.getTransaction().commit();
		}
		finally
		{
			//do nothing
		}
	}
	
	public void removeUser(Doctor user)
	{
		try
		{
			this.em.getTransaction().begin();
			User userx = em.find(Doctor.class, user.getId());
			this.em.remove(userx); 
			this.em.getTransaction().commit();
		}
		finally
		{
			//do nothing
		}
	}
	public void removeUser(Nurse user)
	{
		try
		{
			this.em.getTransaction().begin();
			User userx = em.find(Nurse.class, user.getId());
			this.em.remove(userx); 
			this.em.getTransaction().commit();
		}
		finally
		{
			//do nothing
		}
	}
	
	public void removeUser(SysAdmin user)
	{
		try
		{
			this.em.getTransaction().begin();
			User userx = em.find(SysAdmin.class, user.getId());
			this.em.remove(userx); 
			this.em.getTransaction().commit();
		}
		finally
		{
			//do nothing
		}
	}

	public Map<UserType, User> findUnknownByEmail(String email) throws LoginException{
		Map<UserType, User> map = new HashMap<UserType, User>();
		User user = findPatientByEmail(email);
		if ( user != null ){
			map.put(UserType.PATIENT, user);
			return map;
		}
		
		user = findNurseByEmail(email);
		if ( user != null ){
			map.put(UserType.NURSE, user);
			return map;
		}
		
		user = findDoctorByEmail(email);
		if ( user != null ){
			map.put(UserType.DOCTOR, user);
			return map;
		}
		
		user = findSysAdminByEmail(email);
		if ( user != null ){
			map.put(UserType.SYSTEM_ADMIN, user);
			return map;
		}
		throw new LoginException("No User Found");
	}
	
	public User findByEmail(String email)
	{
		Query findByEmailQuery=null;
		boolean found= true;
		try
		{	
			findByEmailQuery = this.em.createQuery("from User where email = :email");
			findByEmailQuery.setParameter("email", email);
			
		}
		catch(NoResultException e)
		{
			found= false;
			//System.out.println("Email address is not found.");
		}
		User foundUser= null;
		if(found == true)
		{
			try
			{
				foundUser = (User) findByEmailQuery.getSingleResult();
				long userId = foundUser.getId();
				foundUser = this.em.find(User.class, userId);
			}
			catch(NoResultException e)
			{
				//System.out.println("Email address was not found.");
			}
			catch(NonUniqueResultException g)
			{
				//System.out.println("Multiple emails.");
				//List<>foundUser = (User) findByEmailQuery.getSingleResult();
			}
		}
		
		return foundUser;
	}
	
	public Patient findPatientByEmail(String email)
	{
		Patient foundUser= null;
		Query findByEmailQuery=null;
		try
		{	
			findByEmailQuery = this.em.createQuery("from Patient where email LIKE :email");
			findByEmailQuery.setParameter("email", email);
			
			foundUser = (Patient) findByEmailQuery.getSingleResult();
			long userId = foundUser.getId();
			foundUser = this.em.find(Patient.class, userId);
			
		}
		catch(NoResultException e)
		{
			System.out.println("Email address is not found.");
		}
		catch(NonUniqueResultException g)
		{
			System.out.println("Multiple emails.");
			//List<>foundUser = (User) findByEmailQuery.getSingleResult();
		}
		return foundUser;
	}
	
	public Doctor findDoctorByEmail(String email)
	{
		Query findByEmailQuery=null;
		boolean found= true;
		try
		{	
			findByEmailQuery = this.em.createQuery("from Doctor where email = :email and type = :type");
			UserType type= UserType.DOCTOR;
			findByEmailQuery.setParameter("email", email);
			findByEmailQuery.setParameter("type", type);
		}
		catch(NoResultException e)
		{
			found= false;
			System.out.println("Email address is not found.");
		}
		Doctor foundUser= null;
		if(found == true)
		{
			try
			{
				foundUser = (Doctor) findByEmailQuery.getSingleResult();
				long userId = foundUser.getId();
				foundUser = this.em.find(Doctor.class, userId);
			}
			catch(NoResultException e)
			{
				System.out.println("Email address was not found.");
			}
			catch(NonUniqueResultException g)
			{
				System.out.println("Multiple emails.");
				//List<>foundUser = (User) findByEmailQuery.getSingleResult();
			}
		}
		
		return foundUser;
	}
	
	public Nurse findNurseByEmail(String email)
	{
		Query findByEmailQuery=null;
		boolean found= true;
		try
		{	
			findByEmailQuery = this.em.createQuery("from Nurse where email = :email and type = :type");
			UserType type= UserType.NURSE;
			findByEmailQuery.setParameter("email", email);
			findByEmailQuery.setParameter("type", type);
		}
		catch(NoResultException e)
		{
			found= false;
			System.out.println("Email address is not found.");
		}
		Nurse foundUser= null;
		if(found == true)
		{
			try
			{
				foundUser = (Nurse) findByEmailQuery.getSingleResult();
				long userId = foundUser.getId();
				foundUser = this.em.find(Nurse.class, userId);
			}
			catch(NoResultException e)
			{
				System.out.println("Email address was not found.");
			}
			catch(NonUniqueResultException g)
			{
				System.out.println("Multiple emails.");
				//List<>foundUser = (User) findByEmailQuery.getSingleResult();
			}
		}
		
		return foundUser;
	}
	
	public SysAdmin findSysAdminByEmail(String email)
	{
		Query findByEmailQuery=null;
		boolean found= true;
		try
		{	
			findByEmailQuery = this.em.createQuery("from SysAdmin where email = :email and type = :type");
			UserType type= UserType.SYSTEM_ADMIN;
			findByEmailQuery.setParameter("email", email);
			findByEmailQuery.setParameter("type", type);
			
		}
		catch(NoResultException e)
		{
			found= false;
			System.out.println("Email address is not found.");
		}
		SysAdmin foundUser= null;
		if(found == true)
		{
			try
			{
				foundUser = (SysAdmin) findByEmailQuery.getSingleResult();
				long userId = foundUser.getId();
				foundUser = this.em.find(SysAdmin.class, userId);
			}
			catch(NoResultException e)
			{
				System.out.println("Email address was not found.");
			}
			catch(NonUniqueResultException g)
			{
				System.out.println("Multiple emails.");
				//List<>foundUser = (User) findByEmailQuery.getSingleResult();
			}
		}
		
		return foundUser;
	}

	
	private boolean checkDuplicateEmail(String email)
	{
		Query findByEmailQuery=null;
		boolean found= true;

		findByEmailQuery = this.em.createQuery("from User where email = :email");
		findByEmailQuery.setParameter("email", email);
			
		User foundUser= null;

		try
		{
			foundUser = (User) findByEmailQuery.getSingleResult();
			long userId = foundUser.getId();
			System.out.println("Email account already exists. Existing user id: " + Long.toString(userId));
			//foundUser = this.em.find(User.class, userId);
		}
		catch(NoResultException e)
		{
			found= false;
		}
		
		//garbage collection
		foundUser= null;
		
		return found;
	}
	
	public ArrayList<User> getAllUsers()
	{
		
		Query findAllQuery=null;

		findAllQuery = this.em.createQuery("from User where id > :id");
		long limit= -1;
		findAllQuery.setParameter("id", limit);	

		ArrayList<User> users= null;
		

		try
		{
			users = (ArrayList<User>) findAllQuery.getResultList();
		}
		catch(NoResultException e)
		{
			System.out.println("There are no users.");
		}
		catch(NonUniqueResultException g)
		{
			//System.out.println("Multiple emails.");
			//List<>foundUser = (User) findByEmailQuery.getSingleResult();
		}

		
		return users;
	}
	
	public ArrayList<Patient> getAllPatients()
	{
		
		Query findAllQuery=null;

		findAllQuery = this.em.createQuery("from Patient where id > :id and type = :type");
		long limit= -1;
		UserType type= UserType.PATIENT;
		findAllQuery.setParameter("id", limit);
		findAllQuery.setParameter("type", type);	

		ArrayList<Patient> users= null;
		

		try
		{
			users = (ArrayList<Patient>) findAllQuery.getResultList();
		}
		catch(NoResultException e)
		{
			System.out.println("There are no patients.");
		}
		catch(NonUniqueResultException g)
		{
			//System.out.println("Multiple emails.");
			//List<>foundUser = (User) findByEmailQuery.getSingleResult();
		}

		
		return users;
	}
	
	public ArrayList<Doctor> getAllDoctors()
	{
		
		Query findAllQuery=null;

		findAllQuery = this.em.createQuery("from Doctor where id > :id and type = :type");
		long limit= -1;
		UserType type= UserType.DOCTOR;
		findAllQuery.setParameter("id", limit);	
		findAllQuery.setParameter("type", type);
		ArrayList<Doctor> users= null;
		

		try
		{
			users = (ArrayList<Doctor>) findAllQuery.getResultList();
		}
		catch(NoResultException e)
		{
			System.out.println("There are no doctors.");
		}
		catch(NonUniqueResultException g)
		{
			//System.out.println("Multiple emails.");
			//List<>foundUser = (User) findByEmailQuery.getSingleResult();
		}

		
		return users;
	}
	
	public ArrayList<Nurse> getAllNurses()
	{
		
		Query findAllQuery=null;

		findAllQuery = this.em.createQuery("from Nurse where id > :id and type = :type");
		long limit= -1;
		UserType type= UserType.NURSE;
		findAllQuery.setParameter("id", limit);	
		findAllQuery.setParameter("type", type);
		ArrayList<Nurse> users= null;
		

		try
		{
			users = (ArrayList<Nurse>) findAllQuery.getResultList();
		}
		catch(NoResultException e)
		{
			System.out.println("There are no nurses.");
		}
		catch(NonUniqueResultException g)
		{
			//System.out.println("Multiple emails.");
			//List<>foundUser = (User) findByEmailQuery.getSingleResult();
		}

		
		return users;
	}
	
	@SuppressWarnings("unchecked")
	public ArrayList<SysAdmin> getAllSysAdmins()
	{
		
		Query findAllQuery=null;

		findAllQuery = this.em.createQuery("from SysAdmin where id > :id and type = :type");
		long limit= -1;
		UserType type= UserType.SYSTEM_ADMIN;
		findAllQuery.setParameter("id", limit);	
		findAllQuery.setParameter("type", type);
		ArrayList<SysAdmin> users= null;
		
		try
		{
			users = (ArrayList<SysAdmin>) findAllQuery.getResultList();
		}
		catch(NoResultException e)
		{
			System.out.println("There are no system administrators.");
		}
		catch(NonUniqueResultException g)
		{
			//System.out.println("Multiple emails.");
			//List<>foundUser = (User) findByEmailQuery.getSingleResult();
		}

		
		return users;
	}
	
	public Patient findPatientByName(Name nameToFind){
		
		Query findByNameQuery=null;
		findByNameQuery = this.em.createQuery("from User where first LIKE :first_name and middle LIKE :middle_name and last LIKE :last_name");
		findByNameQuery.setParameter("first_name", "%"+nameToFind.getFirst()+"%");
		findByNameQuery.setParameter("middle_name", "%"+nameToFind.getMiddle()+"%");
		findByNameQuery.setParameter("last_name",  "%"+nameToFind.getLast()+"%");
		
		Patient patient= null;
		
		try
		{
			patient = (Patient) findByNameQuery.getSingleResult();
			long userId = patient.getId();
			patient= this.em.find(Patient.class, userId);
		}
		catch(NoResultException e)
		{
			System.out.println("name was not found.");
		}
		catch(NonUniqueResultException g)
		{
			System.out.println("Multiple names.");
			@SuppressWarnings("unchecked")
			// TODO Return list and display a list dialog allowing the user to choose the one they want
			List<Patient> patientList = (List<Patient>) findByNameQuery.getResultList();
			patient = patientList.get(0);
		}
		
		
		
		return patient;
		
	}
	
	@SuppressWarnings("unchecked")
	public ArrayList<Appointment> getDoctorAppointments(Doctor doc){
		Query findAllQuery=null;
	
		findAllQuery = this.em.createQuery("from Appointment where id > :id and requestedDoc_id = :requestedDoc_id");
		long limit= -1;
		long requestedDoc_id= doc.getId();
		findAllQuery.setParameter("id", limit);	
		findAllQuery.setParameter("requestedDoc_id", requestedDoc_id);
		ArrayList<Appointment> appts = new ArrayList<Appointment>();
		
	
		try
		{
			appts = (ArrayList<Appointment>) findAllQuery.getResultList();
		}
		catch(NoResultException e)
		{
			System.out.println("There are no system administrators.");
		}
		catch(NonUniqueResultException g)
		{
			//System.out.println("Multiple emails.");
			//List<>foundUser = (User) findByEmailQuery.getSingleResult();
		}
		
		return appts;
		
	}


	public void removeAppointment(Appointment appt){
		try
		{
			this.em.getTransaction().begin();
			Appointment a = em.find(Appointment.class, appt.getId());
			this.em.remove(a); 
			this.em.getTransaction().commit();
		}
		finally
		{
			//do nothing
		}
	}
	
	public boolean createTreatmentRecord(TreatmentRecord tr) {
		this.em.getTransaction().begin();
		this.em.persist(tr);
		this.em.getTransaction().commit();
		return true;
	}

}
